package fr.utbm.tx52.stamp.logic.reception;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import android.util.Pair;
import android.widget.Toast;
import fr.utbm.tx52.stamp.R;
import fr.utbm.tx52.stamp.logic.Contact;
import fr.utbm.tx52.stamp.logic.DataManager;
import fr.utbm.tx52.stamp.logic.utils.FileUtil;
import fr.utbm.tx52.stamp.ui.activity.Main;
import fr.utbm.tx52.stamp.ui.activity.Viewer;
import fr.utbm.tx52.stamp.ui.fragment.AddressBook;
import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Service qui va surveiller la reception de message en mode pull. C'est ce
 * service qui a la socket de communication et qui envoi et re�oit les messages
 * du serveur
 *
 * @author Xavier MICHEL (xavier.michel@utbm.fr) David LACHICHE
 * (david.lachiche@utbm.fr)
 */
public class PullService extends Service
{

	/**
	 * Port utilis� sur le serveur
	 */
	private static final int port = 1337;
	/**
	 * Adresse du serveur
	 */
	private static final String IPServer = "192.168.0.11";
	/**
	 * Identifiant notification logiciel
	 */
	private static final int ID_NOTIFICATION = 1989;
	/**
	 * Instance du service
	 */
	private static PullService m_instance = null;
	/**
	 * La socket de communcation avec le serveur
	 */
	Socket m_socket;
	/**
	 * Flux de sortie
	 */
	private ObjectOutputStream m_out;
	/**
	 * Flux d'entr�e
	 */
	private ObjectInputStream m_in;

	/**
	 * Obtient l'instance du service
	 */
	public static PullService getInstance()
	{
		return m_instance;
	}

	@Override
	public void onCreate()
	{
		Log.d(this.getClass().getName(), "Cr�ation du service");
		super.onCreate();
		m_socket = null;
		m_instance = this;
	}

	/**
	 * Le corp du service
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId)
	{
		Log.d(this.getClass().getName(), "Service d�marr�");

		final Context context = this;

		// passe en thread car readline est bloquant
		Thread t = new Thread(new Runnable()
		{

			public void run()
			{
				while (true)
				{
					Log.d(this.getClass().getName(), "Cr�ation de la socket...");
					try
					{
						m_socket = new Socket(IPServer, port);
						m_out = new ObjectOutputStream(m_socket.getOutputStream());
						m_in = new ObjectInputStream(m_socket.getInputStream());
						Log.d(this.getClass().getName(), "La socket a bien �t� cr��e !");
					}
					catch (Exception e)
					{
						Log.d(this.getClass().getName(), "Erreur lors de la cr�ation de la socket : "
							+ e.getMessage());
						m_socket = null;
					}

					sendIdentification();

					Object message;
					try
					{
						while (true)
						{
							message = m_in.readObject();

							if (message == null)
							{
								continue;
							}

							Log.d(this.getClass().getName(), "Message recu : '" + message.toString() + "'");

							if (message instanceof String)
							{
								String strMessage = (String) message;

								// demande d'ajout d'un ami
								if (strMessage.startsWith("+"))
								{
									Log.d(this.getClass().getName(), "Demande d'ajout d'un ami");
									String phone = strMessage.split(" ")[1];
									AddressBook.addContactInWaitingList(strMessage.split(" ")[1]);
									createNotification(getApplicationContext(), "Demande d'ajout d'ami",
										Contact.getContactsByPhone(context, phone).getName()
										+ " souhaite pouvoir vous envoyer des photos.", Main.class);
								}
								// on doit recevoir une photo avec commentaire et tout
								else if (strMessage.startsWith("@"))
								{
									/*
									 * param1 = from param2 = nom du fichier
									 * image param3 = nom du fichier son associ�
									 * ou null si aucun reste = commentaire
									 * �crit
									 */

									String[] args = strMessage.split(" ");

									String comment = "";
									for (int i = 4; i < args.length; ++i)
									{
										comment += args[i] + " ";
									}


									// Par ici, Par ici !
									FileOutputStream fosComm = new FileOutputStream(
										DataManager.DATA_PATH + FileUtil.getCommentPath(args[2]));
									fosComm.write((args[1] + "\n").getBytes());
									fosComm.write(comment.getBytes());
									fosComm.flush();
									fosComm.close();


									// enregistrement de l'image
									FileOutputStream fosImg = new FileOutputStream(
										DataManager.DATA_PATH + args[2]);
									byte[] buffer = (byte[]) m_in.readObject();
									fosImg.write(buffer);
									fosImg.flush();
									fosImg.close();

									// enregistrement du commentaire audio
									if (!args[3].equals("null"))
									{
										args[3] = FileUtil.getSoundPath(args[2]);
										FileOutputStream fosSnd = new FileOutputStream(DataManager.DATA_PATH + args[3]);
										byte[] bufferSnd = (byte[]) m_in.readObject();
										fosSnd.write(bufferSnd);
										fosSnd.flush();
										fosSnd.close();
									}

									Log.d(getClass().getName(), "===============================================");
									Log.d(getClass().getName(), "Envoy� par : " + args[1]);
									Log.d(getClass().getName(), "Nom du fichier image : " + args[2]);
									Log.d(getClass().getName(), "Nom du fichier son : " + args[3]
										+ (args[3].equals("null") ? " (aucun)" : ""));
									Log.d(getClass().getName(), "Commentaire �crit : " + comment);
									Log.d(getClass().getName(), "===============================================");

									List<Pair<String, String>> params = new ArrayList<Pair<String, String>>();
									params.add(new Pair<String, String>(Viewer.COMMENT_KEY, comment));
									params.add(new Pair<String, String>(Viewer.FROM_KEY, args[1]));
									params.add(new Pair<String, String>(Viewer.PATH_KEY, args[2]));
									params.add(new Pair<String, String>(Viewer.AUDIO_COMMENT_KEY, args[3]));

									createNotification(getApplicationContext(), "Nouvelle photo", Contact.getContactsByPhone(context, args[1]).getName()
										+ " a partag� une photo avec vous !", Viewer.class, params);
								}
							}
						}
					}
					catch (Exception e)
					{
						Log.d(this.getClass().getName(), "Erreur pendant l'execution du service : " + e.getMessage());
					}
					finally
					{
						m_socket = null;
					}

					// il y a eu une erreur, on va donc attendre un peu avant de tenter une reconnexion
					try
					{
						Thread.sleep(5000);
					}
					catch (InterruptedException e)
					{
					}
				}
			}
		});

		t.start();

		return START_NOT_STICKY;
	}

	/**
	 * Envoi l'identification au serveur, c'est � dire le num�ro de t�l�phone
	 */
	public static synchronized void sendIdentification()
	{
		PullService ps = getInstance();

		if (ps.m_socket == null)
		{
			return;
		}

		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ps.getApplicationContext());
		String number = prefs.getString("phonenumber", "");
		Log.d(ps.getClass().getName(), "phone number : " + number);
		if (!"".equals(number))
		{
			ps.sendMessage(MessageType.IDENTIFICATION, number);
		}
	}

	/**
	 * Envoi un message au serveur
	 *
	 * @param type Le type du message, par exemple # pour s'identifier
	 * @param message Le message a passer en argument
	 */
	public void sendMessage(MessageType type, String message)
	{

		if (m_socket == null)
		{
			Toast.makeText(getApplicationContext(),
				R.string.connexion_failed, Toast.LENGTH_LONG).show();
			return;
		}

		synchronized (m_out)
		{
			Log.d(getClass().getName(), "Sending message : " + type.getSymbol() + " " + message);
			try
			{
				m_out.writeObject((type.getSymbol() + " " + message));
				m_out.flush();
			}
			catch (IOException e)
			{
				Log.d(getClass().getName(), "Erreur lors de l'envoi du message : "
					+ type.getSymbol() + " " + message + " -> " + e.getMessage());
			}
		}
	}

	/**
	 * Envoi un message au serveur
	 *
	 * @param type Le type du message, par exemple # pour s'identifier
	 * @param args Les arguments a envoyer, exemple le num�ro de t�l�phone si on
	 * a envoy� #
	 */
	public void sendMessage(MessageType type, List<String> args)
	{
		if (m_socket == null)
		{
			Toast.makeText(getApplicationContext(),
				"Impossible de se connecter au serveur, le message ne sera donc pas envoy�", Toast.LENGTH_LONG).show();
			return;
		}

		String message = type.getSymbol() + " ";
		for (Iterator<String> it = args.iterator(); it.hasNext();)
		{
			message += it.next() + " ";
		}
		synchronized (m_out)
		{
			Log.d(getClass().getName(), "Sending message : " + message);
			try
			{
				m_out.writeObject(message);
				m_out.flush();
			}
			catch (IOException e)
			{
				Log.d(getClass().getName(), "Erreur lors de l'envoi du message : " + message + " -> " + e.getMessage());
			}
		}
	}

	/**
	 * Envoi d'un fichier dans la socket
	 *
	 * @param path Le chemin du fichier
	 * @param message Le message qui sera affich� sous la barre de progression
	 * @param type Le type du message (FILE_SOUND ou FILE_IMAGE)
	 */
	public void sendFile(String path, MessageType type)
	{
		if (m_socket == null)
		{
			/*
			 * Toast.makeText(getApplicationContext(), "Impossible de se
			 * connecter au serveur, le message ne sera donc pas envoy�", Toast.LENGTH_LONG);
			 */
			return;
		}

		synchronized (m_out)
		{
			File f = new File(path);

			if (!f.exists())
			{
				Log.e(getClass().getName(), "Tentative d'envoi d'un fichier inexistant : " + path);
				return;
			}

			try
			{
				// on annonce qu'on va envoyer un son
				m_out.writeObject(type.getSymbol());
				m_out.flush();

				Log.d(this.getClass().getName(), "Envoi du fichier " + f.toURI().toURL());

				long fileSize = f.length();
				Log.d(this.getClass().getName(), "Taille du fichier : " + fileSize);

				FileInputStream fis = new FileInputStream(path);

				byte[] message = new byte[(int) f.length()];
				fis.read(message);
				m_out.writeObject(message);
				m_out.flush();
			}
			catch (Exception e)
			{
				Log.d(this.getClass().getName(), "Erreur lors de l'envoi du fichier : " + e.getMessage());
			}
		}
	}

	@Override
	public void onDestroy()
	{
		Log.d(this.getClass().getName(), "Le service est terminé !");
		try
		{
			m_socket.close();
		}
		catch (IOException e)
		{
		}
	}

	@Override
	public IBinder onBind(Intent arg0)
	{
		return null;
	}

	/**
	 * Cr�ation d'une notification
	 *
	 * @param title Le titre de la notification
	 * @param content Le contenu de la notification
	 * @param target L'activit� � ouvrir quand on clique sur la notification
	 * @param params Les param�tres a passer � l'activit� <clef, valeur>
	 */
	public static void createNotification(Context context, String title, String content, Class<?> target, List<Pair<String, String>> params)
	{

		cancelNotification(context);

		// On cr�er un "gestionnaire de notification"
		NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

		// On cr�er la notification avec son ic�ne et son texte d�filant
		// (optionel si l'on veut pas de texte d�filant on met cet argument � null)
		Notification.Builder builder = new Notification.Builder(context);
		builder.setTicker(content);
		builder.setContentTitle(title);
		builder.setContentText(content);
		builder.setSmallIcon(R.drawable.ic_notification);



		// Le PendingIntent c'est ce qui va nous permettre d'atteindre notre deuxi�me Activity
		// ActivityNotification sera donc le nom de notre seconde Activity
		Intent i = new Intent(context, target);

		for (Pair<String, String> p : params)
		{
			i.putExtra(p.first, p.second);
		}

		PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, i, Intent.FLAG_ACTIVITY_NEW_TASK);

		// On configure notre notification avec tous les param�tres que l'on vient de cr�er
		builder.setContentIntent(pendingIntent);


		/*
		 * On ajoute un style de vibration � notre notification
		 *
		 * L'utilisateur est donc �galement averti par les vibrations de son
		 * t�l�phone Ici les chiffres correspondent � 0sec de pause, 0.2sec de
		 * vibration, 0.1sec de pause, 0.2sec de vibration, 0.1sec de pause,
		 * 0.2sec de vibration
		 */
		// notification.vibrate = new long[] {0,200,100,200,100,200};

		// Enfin on ajoute notre notification et son ID � notre gestionnaire de notification
		notificationManager.notify(ID_NOTIFICATION, builder.getNotification());
	}

	/**
	 * Cr�ation d'une notification
	 *
	 * @param title Le titre de la notification
	 * @param content Le contenu de la notification
	 * @param target L'activit� � ouvrir quand on clique sur la notification
	 */
	public static void createNotification(Context context, String title, String content, Class<?> target)
	{

		cancelNotification(context);

		// On cr�er un "gestionnaire de notification"
		NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

		// On cr�er la notification avec son ic�ne et son texte d�filant
		// (optionel si l'on veut pas de texte d�filant on met cet argument � null)
		Notification.Builder builder = new Notification.Builder(context);
		builder.setSmallIcon(R.drawable.ic_notification);
		builder.setTicker(content);
		builder.setContentText(content);
		builder.setContentTitle(title);


		// Le PendingIntent c'est ce qui va nous permettre d'atteindre notre deuxi�me Activity
		// ActivityNotification sera donc le nom de notre seconde Activity
		PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(context, target), 0);

		// On configure notre notification avec tous les param�tres que l'on vient de cr�er
		builder.setContentIntent(pendingIntent);

		/*
		 * On ajoute un style de vibration � notre notification
		 *
		 * L'utilisateur est donc �galement averti par les vibrations de son
		 * t�l�phone Ici les chiffres correspondent � 0sec de pause, 0.2sec de
		 * vibration, 0.1sec de pause, 0.2sec de vibration, 0.1sec de pause,
		 * 0.2sec de vibration
		 */
		// notification.vibrate = new long[] {0,200,100,200,100,200};

		// Enfin on ajoute notre notification et son ID � notre gestionnaire de notification
		notificationManager.notify(ID_NOTIFICATION, builder.getNotification());
	}

	/**
	 * Supprime la notification cr��e par le logiciel
	 *
	 * @param c Le context de l'application
	 */
	public static void cancelNotification(Context c)
	{
		// On cr�� notre gestionnaire de notfication
		NotificationManager notificationManager = (NotificationManager) c.getSystemService(Context.NOTIFICATION_SERVICE);
		// on supprime la notification gr�ce � son ID
		notificationManager.cancel(ID_NOTIFICATION);
	}
}
